package com.zds.passwordbox.common.util;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class TOTPUtil {
    private static final String ALGORITHM = "HmacSHA1";
    private static final int TIME_STEP = 30; // 每30秒一个时间步

    public static boolean verifyCode(String enteredCode, String secretKeyStr) {
        byte[] secretKey = base32Decode(secretKeyStr);
        long timeIndex = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) / TIME_STEP;


        // 向前和向后验证几个时间步长（这里取前后各1个时间步长作为示例，可根据实际需求调整）
        for (long offset = -1; offset <= 1; offset++) {
            long candidateTimeStep = timeIndex + offset;
            String generatedCode;
            try {
                generatedCode = generateTOTP(secretKey, candidateTimeStep);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            if (generatedCode.equals(enteredCode)) {
                return true;
            }
        }
        return false;
    }

    public static String generateCode(String secretKeyStr) {
        // Base32 解码
        byte[] secretKey = base32Decode(secretKeyStr);
        long timeIndex = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) / TIME_STEP;
        try {
            return generateTOTP(secretKey, timeIndex);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static String generateTOTP(byte[] decodedKey, long timeIndex)
            throws InvalidKeyException, NoSuchAlgorithmException {
        byte[] timeBytes = longToByteArray(timeIndex);

        // 生成 HMAC
        Mac mac = Mac.getInstance(ALGORITHM);
        SecretKeySpec keySpec = new SecretKeySpec(decodedKey, ALGORITHM);
        mac.init(keySpec);
        byte[] hmacResult = mac.doFinal(timeBytes);

        // 生成 OTP 代码
        int offset = hmacResult[hmacResult.length - 1] & 0x0F;
        int binary = ((hmacResult[offset] & 0x7F) << 24) |
                ((hmacResult[offset + 1] & 0xFF) << 16) |
                ((hmacResult[offset + 2] & 0xFF) << 8) |
                (hmacResult[offset + 3] & 0xFF);

        int otp = binary % 1000000; // 生成6位数字
        return String.format("%06d", otp);
    }

    private static byte[] longToByteArray(long l) {
        return new byte[]{
                (byte) (l >>> 56),
                (byte) (l >>> 48),
                (byte) (l >>> 40),
                (byte) (l >>> 32),
                (byte) (l >>> 24),
                (byte) (l >>> 16),
                (byte) (l >>> 8),
                (byte) l
        };
    }

    private static final String BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

    /**
     * 校验输入的字符串是否是合法的Base32编码字符串
     *
     * @param base32 要校验的字符串
     * @return 如果是合法的Base32编码字符串则返回true，否则返回false
     */
    public static boolean isValidBase32(String base32) {
        if (base32 == null || base32.isEmpty()) {
            return false;
        }

        base32 = base32.trim().toUpperCase();

        // 检查是否只包含Base32字符表中的字符以及填充字符 '='
        for (int i = 0; i < base32.length(); i++) {
            char c = base32.charAt(i);
            if (c!= '=' && BASE32_ALPHABET.indexOf(c) == -1) {
                return false;
            }
        }

        // 检查填充字符 '=' 的格式是否正确
        int paddingCount = 0;
        for (int i = base32.length() - 1; i >= 0 && base32.charAt(i) == '='; i--) {
            paddingCount++;
        }

        // 根据Base32编码规则，填充字符数量应该是0、4、7或者8的倍数减1（即最后一组不足8个字符时的填充情况）
        return paddingCount == 0 || paddingCount % 8 == 4 || paddingCount % 8 == 7;
    }

    /**
     * 将Base32编码的字符串解码为字节数组
     *
     * @param base32 输入的Base32编码字符串
     * @return 解码后的字节数组，如果输入的Base32字符串格式不正确则返回null
     */
    public static byte[] base32Decode(String base32) {
        if (base32 == null) {
            return null;
        }
        base32 = base32.trim().toUpperCase();
        int paddingCount = 0;
        // 计算填充字符（'='）的数量
        for (int i = base32.length() - 1; i >= 0 && base32.charAt(i) == '='; i--) {
            paddingCount++;
        }
        // 移除填充字符
        base32 = base32.replaceAll("=", "");

        // 计算解码后字节数组的预期长度
        int outputLength = base32.length() * 5 / 8;
        byte[] output = new byte[outputLength];

        int buffer = 0;
        int bitsLeft = 0;
        int index = 0;

        for (char c : base32.toCharArray()) {
            int charValue = BASE32_ALPHABET.indexOf(c);
            if (charValue == -1) {
                return null;
            }
            buffer <<= 5;
            buffer |= charValue;
            bitsLeft += 5;
            while (bitsLeft >= 8) {
                bitsLeft -= 8;
                output[index++] = (byte) ((buffer >> bitsLeft) & 0xFF);
            }
        }

        // 如果还有剩余的位，处理最后一个字节（可能需要根据填充情况调整）
        if (bitsLeft > 0) {
            output[index] = (byte) (buffer << (8 - bitsLeft));
        }

        // 根据填充情况调整最终的输出字节数组长度
        if (paddingCount > 0) {
            byte[] adjustedOutput = new byte[outputLength - paddingCount];
            System.arraycopy(output, 0, adjustedOutput, 0, adjustedOutput.length);
            return adjustedOutput;
        }

        return output;
    }
}
